/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of The Qt Company Ltd nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtWidgets>

#include "codeeditor.h"
#include "utils.hpp"

//![constructor]

CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent)
{
    filepath = "";
    fileType = "";
    highlighter = NULL;

    QFont edit_font = load_font(QString(":/Anonymous-Pro.ttf"));
    lineNumberArea = new LineNumberArea(this);
    lineNumberArea->setFont(edit_font);
    this->setFont(edit_font);

    connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
    connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));

    updateLineNumberAreaWidth(0);
    highlightCurrentLine();
}

void CodeEditor::setMainWindowStyle(QString backgroundColor, QString lineColor, QString foregroundColor){
    QString stylesheet ="QPlainTextEdit {"
                        "background-color: " +  backgroundColor  +";"
                        "color:" + foregroundColor  +";"
                        "border: 0px;"
                        "padding-left: 1px;"
                        "selection-background-color: #404f4f;"
                        "font-size:12pt;"
                     "}"

             "QScrollBar:vertical{"
                "background: " +  backgroundColor  +";"
                "width: 12px;"
                "margin: 0px 0px 0px 0px;"
             "}"
             "QScrollBar:horizontal{"
                "background: " +  backgroundColor  +";"
                "height:12px;"
                "margin: 0px 0px 0px 0px;"
             "}"

             "QScrollBar::sub-page:vertical {"
                "background: " +  backgroundColor + ";"
             "}"
             "QScrollBar::add-page:vertical {"
                "background: " +  backgroundColor + ";"
             "}"
             "QScrollBar::sub-page:horizonal {"
                "background: " +  backgroundColor +";"
             "}"
             "QScrollBar::add-page:horizontal {"
                "background: " +  backgroundColor +";"
             "}"

             "QScrollBar::handle:vertical {"
                "background:" + lineColor  +";"
                "border: 2px solid " +  backgroundColor  +";"
                "border-radius: 5px 5px 5px 5px;"
                "min-height: 30px;"
             "}"
             "QScrollBar::handle:horizontal {"
                "background:" + lineColor  +";"
                 "border: 2px solid " +  backgroundColor  +";"
                 "border-radius: 5px 5px 5px 5px;"
                 "min-height: 30px;"
             "}";

    CodeEditor::setStyleSheet(stylesheet);

}

void CodeEditor::setTabWidgetStyle(QString foregroundColor, QString backgroundColor){
    QString stylesheet = "QPlainTextEdit {"
                "background-color: " +  backgroundColor  +";"
                "color:" + foregroundColor  +";"
                "border: 0px;"
                "padding-left: 1px;"
                "selection-background-color: #404f4f;"
                "font-size:14pt;"
             "}"
             "QScrollBar:vertical{"
                "background: " +  backgroundColor  +";"
             "}"
             "QScrollBar:horizontal{"
                "background: " +  backgroundColor  +";"
             "}"
             "QTabBar::tab:selected{"
                "color: white;"
                "border-bottom: 3px solid #2F4F4F;"
             "}"
             "QTabBar::tab {"
                "height: 22px; width: 160px;"
                "color: #676767;"
                "font-size:9pt;"
                "margin: 0 -2px;"
                "padding: 1px 5px;"
                "background-color: #262626;"
                "border-bottom: 3px solid #212121;"
             "}";
//   ui->tabWidget->setStyleSheet(stylesheet);

}

//![constructor]

//![extraAreaWidth]

int CodeEditor::lineNumberAreaWidth()
{
    int digits = 1;
    int max = qMax(1, blockCount());
    while (max >= 10) {
        max /= 10;
        ++digits;
    }

    int space = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits;

    return space;
}

//![extraAreaWidth]

//![slotUpdateExtraAreaWidth]

void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
{
    setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}

//![slotUpdateExtraAreaWidth]

//![slotUpdateRequest]

void CodeEditor::updateLineNumberArea(const QRect &rect, int dy)
{
    if (dy)
        lineNumberArea->scroll(0, dy);
    else
        lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());

    if (rect.contains(viewport()->rect()))
        updateLineNumberAreaWidth(0);
}

//![slotUpdateRequest]

//![resizeEvent]

void CodeEditor::resizeEvent(QResizeEvent *e)
{
    QPlainTextEdit::resizeEvent(e);

    QRect cr = contentsRect();
    lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
}

//![resizeEvent]

//![cursorPositionChanged]

void CodeEditor::highlightCurrentLine()
{
    QList<QTextEdit::ExtraSelection> extraSelections;

    if (!isReadOnly()) {
        QTextEdit::ExtraSelection selection;

        QColor lineColor = QColor(250,250,250);

        selection.format.setBackground(lineColor);
        selection.format.setProperty(QTextFormat::FullWidthSelection, true);
        selection.cursor = textCursor();
        selection.cursor.clearSelection();
        extraSelections.append(selection);
    }

    setExtraSelections(extraSelections);
}

//![cursorPositionChanged]

//![extraAreaPaintEvent_0]

void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
{
    QPainter painter(lineNumberArea);
    painter.fillRect(event->rect(), Qt::lightGray);

//![extraAreaPaintEvent_0]

//![extraAreaPaintEvent_1]
    QTextBlock block = firstVisibleBlock();
    int blockNumber = block.blockNumber();
    int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
    int bottom = top + (int) blockBoundingRect(block).height();
//![extraAreaPaintEvent_1]

//![extraAreaPaintEvent_2]
    while (block.isValid() && top <= event->rect().bottom()) {
        if (block.isVisible() && bottom >= event->rect().top()) {
            QString number = QString::number(blockNumber + 1);
            painter.setPen(Qt::black);
            painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
                             Qt::AlignRight, number);
        }

        block = block.next();
        top = bottom;
        bottom = top + (int) blockBoundingRect(block).height();
        ++blockNumber;
    }
}
//![extraAreaPaintEvent_2]


// Selects Current Line Where Cursor Is Positioned
void CodeEditor::selectLine(){
   QTextCursor cur = this->textCursor();
   cur.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
   cur.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
   this->setTextCursor(cur);
}

// Selects Current Word Where Cursor Is Positioned
void CodeEditor::selectWord(){
   QTextCursor cur = this->textCursor();
   cur.movePosition(QTextCursor::StartOfWord, QTextCursor::MoveAnchor);
   cur.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
   this->setTextCursor(cur);
}

// Returns Word Under Cursor
QString CodeEditor::lineUnderCursor(){
   selectLine();
   QTextCursor cur = this->textCursor();
   QString line = cur.selectedText();
   cur.clearSelection();
   this->setTextCursor(cur);
   return line;
}

// Returns Word Under Cursor
QString CodeEditor::wordUnderCursor(){
   selectWord();
   QTextCursor cur = this->textCursor();
   QString word = cur.selectedText();
   cur.clearSelection();
   this->setTextCursor(cur);
   return word;
}

// Deletes Current Line Where Cursor Is Positioned
void CodeEditor::deleteLine(){
   selectLine();
   QTextCursor cur = this->textCursor();
   if(cur.selectedText() != ""){
       cur.removeSelectedText();
   }
   this->setTextCursor(cur);
}

// Deletes Current Word Where Cursor Is Positioned
void CodeEditor::deleteWord(){
   selectWord();
   QTextCursor cur = this->textCursor();

   if (cur.selectedText() != ""){
       cur.removeSelectedText();
   }

   this->setTextCursor(cur);
}

// Joins Line Where Cursor Is Currently Positioned With The Line Below It
void CodeEditor::joinLines(){
   QTextCursor cur = this->textCursor();

   cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);
   cur.deleteChar();

   this->setTextCursor(cur);
}

// Swaps Line Where Cursor Is Currently Positioned With The Line Above It
void CodeEditor::swapLineUp(){
   QTextCursor cur = this->textCursor();
   //  Select Current Line And Store Value
   cur.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
   cur.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
   QString newTop = cur.selectedText();
   cur.removeSelectedText();
   // Select Line Above And Store Value
   cur.movePosition(QTextCursor::Up, QTextCursor::MoveAnchor);
   cur.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
   cur.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
   QString newBottom = cur.selectedText();
   cur.removeSelectedText();
   // Insert New Values
   cur.insertText(newTop);
   cur.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor);
   cur.insertText(newBottom);
   // Position Cursor
   cur.movePosition(QTextCursor::Up, QTextCursor::MoveAnchor);
   cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);

   this->setTextCursor(cur);
}

// Swaps Line Where Cursor Is Currently Positioned With The Line Below It
void CodeEditor::swapLineDown(){
   QTextCursor cur = this->textCursor();
   //  Select Current Line And Store Value
   cur.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
   cur.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
   QString newBottom = cur.selectedText();
   cur.removeSelectedText();
   // Select Line Below And Store Value
   cur.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor);
   cur.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
   cur.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
   QString newTop = cur.selectedText();
   cur.removeSelectedText();
   // Insert New Values
   cur.insertText(newBottom);
   cur.movePosition(QTextCursor::Up, QTextCursor::MoveAnchor);
   cur.insertText(newTop);
   // Position Cursor
   cur.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor);
   cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);

   this->setTextCursor(cur);
}



/*
*  Toggles currently selected line(s) between commented out and uncommented
*  Comments based on language of file type
*  If no text is selected then it selects the current line
*/
void CodeEditor::toggleComment(){
   QTextCursor cur = this->textCursor();
   if (!cur.hasSelection()){
       selectLine();
   }

   int endSelection = cur.selectionEnd();
   cur.setPosition(cur.selectionStart());
   cur.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
   this->setTextCursor(cur);

   QString line = lineUnderCursor();

   QString commentStart;
   QString commentEnd;

   if(fileType == "html" or fileType == "css"){

       if(fileType == "html"){
           commentStart = "<!--";
           commentEnd = "-->";
       }else{
           commentStart = "/*";
           commentEnd = "*/";
       }

       if(line.startsWith(commentStart)){
           for(int i=0; i < commentStart.length(); i++){
               cur.deleteChar();
           }
           endSelection -= commentStart.length();

           cur.setPosition(endSelection);
           cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);
           this->setTextCursor(cur);
           QString line = lineUnderCursor();

           if(line.endsWith(commentEnd)){
               for(int i=0; i < commentEnd.length(); i++){
                   cur.deletePreviousChar();
               }
           }
       }else{
           cur.insertText(commentStart);
           endSelection += commentStart.length();

           cur.setPosition(endSelection);
           cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);
           this->setTextCursor(cur);
           QString line = lineUnderCursor();

           if(!line.endsWith(commentEnd)){
               cur.insertText(commentEnd);
           }
       }

   }else{
       if(fileType == "py"){
           commentStart = "#";
       }else{
           commentStart = "//";
       }

       if(line.startsWith(commentStart)){
           for(int i=0; i < commentStart.length(); i++){
               cur.deleteChar();
           }
           endSelection -= commentStart.length();
           cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);

           while(cur.position() < endSelection){
               cur.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor);
               this->setTextCursor(cur);
               line = lineUnderCursor();

               if(line.startsWith(commentStart)){
                   cur.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
                   for(int i=0; i < commentStart.length(); i++){
                       cur.deleteChar();
                   }
                   endSelection -= commentStart.length();
               }
               cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);

           }

       }else{
           cur.insertText(commentStart);
           endSelection += commentStart.length();
           cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);

           while(cur.position() < endSelection){
               cur.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor);
               this->setTextCursor(cur);
               line = lineUnderCursor();

               if(!line.startsWith(commentStart)){
                   cur.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
                   cur.insertText(commentStart);
                   endSelection += commentStart.length();
               }

               cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);
           }
       }

   }

   this->setTextCursor(cur);
}

void CodeEditor::setHighlighterTheme(QString theme){
   if (highlighter != NULL){
       highlighter->setTheme(fileType, theme);
   }
}

void CodeEditor::setHighlighter(Highlighter * h){
   if(highlighter != NULL){
       delete highlighter;
   }

   highlighter = h;
}

/*
*****************************
*         File Info         *
*****************************
*/
QString CodeEditor::getFilepath(){
   return filepath;
}

QString CodeEditor::getFileType(){
   return fileType;
}

void CodeEditor::setFilePath(QString newFilepath){
   filepath = newFilepath;
}
void CodeEditor::setFileType(QString newFileType){
   fileType = newFileType;
}

/*
*****************************
*         Virtual functions *
*****************************
*/

void CodeEditor::keyPressEvent ( QKeyEvent * e ){
   // Indent Selection On Tab
   if(e->key() == 16777217){
       QTextCursor cur = this->textCursor();
       if (cur.hasSelection()){
           int endSelection = cur.selectionEnd();
           cur.setPosition(cur.selectionStart());
           cur.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
           cur.insertText("\t");
           endSelection++;
           cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);
           while(cur.position() < endSelection){
               cur.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor);
               cur.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
               cur.insertText("\t");
               endSelection++;
               cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);
           }

           this->setTextCursor(cur);

       }else{
           QPlainTextEdit::keyPressEvent(e);
       }

   }else if(e->key() == 16777218){
       // Unindent Selection On Backtab
       QTextCursor cur = this->textCursor();
       if (!cur.hasSelection()){
           selectLine();
       }
       int endSelection = cur.selectionEnd();
       cur.setPosition(cur.selectionStart());
       cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);
       cur.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
       QString line = cur.selectedText();
       cur.clearSelection();
       if(line.startsWith("\t")){
           cur.deleteChar();
           endSelection -= 1;
       }

       cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);
       while(cur.position() < endSelection){
           cur.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor);
           cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);
           cur.movePosition(QTextCursor::StartOfLine, QTextCursor::KeepAnchor);
           QString line = cur.selectedText();
           cur.clearSelection();
           if(line.startsWith("\t")){
               cur.deleteChar();
               endSelection -= 1;
           }
           cur.movePosition(QTextCursor::EndOfLine, QTextCursor::MoveAnchor);
       }

       this->setTextCursor(cur);

   }else{
       QPlainTextEdit::keyPressEvent(e);
   }
}


